#ifndef GenPatches_HPP
#define GenPatches_HPP

#include "GenPatch.hpp"
#include "UpdateDesc.hpp"

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <dirent.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <list>
#include <set>
#include <stack>
#include <string>
#include <fstream>
#include <errno.h>
#if defined(SOLARIS) || defined(SunOS)
#include <libgen.h>
#endif

using namespace std;
//using namespace std::memcmp;
#ifdef _WIN32
#define PATH_SEPARATOR   '/'  // Here we just use the same style
#else
#define PATH_SEPARATOR   '/'
#endif

class GenPatches
{

private:

    string _old_dir;
    string _new_dir;
    string _patch_dir;
    string _update_desc_dir;
    string _update_desc_filename;
    list<string> _old_files;
    list<string> _new_files;
    list<string> _patch_files;

    UpdateDesc _UpdateDesc;

public:

    GenPatches() {}

    GenPatches(const char *old_dir, const char *new_dir, const char *patch_dir, const char *update_desc_dir)
    {
        _old_dir = old_dir;
        _new_dir = new_dir;
        _patch_dir = patch_dir;
        _update_desc_dir = update_desc_dir;
        if (!IsFileExist(old_dir))
            pear_mkdirp(old_dir, 0777);
        if (!IsFileExist(new_dir))
            pear_mkdirp(new_dir, 0777);
        if (!IsFileExist(patch_dir))
            pear_mkdirp(patch_dir, 0777);
        if (!IsFileExist(update_desc_dir))
            pear_mkdirp(update_desc_dir, 0777);
    }
    ~GenPatches() {}

    void AddDesc(const string &CurrentVersion, const string &PreviousVersion, const string &Platform, const string &URI)
    {
        _UpdateDesc.AddDesc(CurrentVersion, PreviousVersion, Platform, URI);
        _update_desc_filename = Platform + "." + PreviousVersion + "." + CurrentVersion + ".xml";
    }

    int WriteUpdateDesc()
    {
        string file;
        if (_update_desc_filename == "")
            _update_desc_filename = "xxx.xml";
        file = _update_desc_dir + '/' + _update_desc_filename;
        return _UpdateDesc.Write2XMLFile(file.c_str());
    }

    static int pear_mkdir(const char *dir, mode_t mode)
    {
#if defined(MINGW) || defined(__MINGW32__) || defined(__MINGW64__)
        return mkdir(dir);
#elif defined(_MSC_VER)
        return _mkdir(dir);
#else
        return mkdir(dir, mode); // notice that 777 is different than 0777
#endif
    }

    static char *pear_strdup(const char *str)
    {
        int len = strlen(str) + 1;
        char *buf = (char *)malloc(len);
        if (buf) memcpy(buf, str, len);
        return buf;
    }

    static char *path_normalize(const char *path)
    {
        if (!path) return NULL;

        char *copy = pear_strdup(path);
        if (NULL == copy) return NULL;
        char *ptr = copy;

        for (int i = 0; copy[i]; i++)
        {
            *ptr++ = path[i];
            if ('/' == path[i])
            {
                i++;
                while ('/' == path[i]) i++;
                i--;
            }
        }
        *ptr = '\0';
        return copy;
    }

    static int pear_mkdirp(const char *path, mode_t mode)
    {
#if defined(SOLARIS) || defined(SunOS)
        return mkdirp(path, mode);
#else
        char *pathname = NULL;
        char *parent = NULL;

        if (NULL == path) return -1;

        pathname = path_normalize(path);
        if (NULL == pathname) goto fail;

        parent = pear_strdup(pathname);
        if (NULL == parent) goto fail;

        char *p;
        p = parent + strlen(parent);
        while ('/' != *p && p != parent)
        {
            p--;
        }
        *p = '\0';

        // make parent dir
        if (p != parent && 0 != pear_mkdirp(parent, mode)) goto fail;
        free(parent);

        int rc;
        rc = pear_mkdir(pathname, mode);
        free(pathname);

        return (0 == rc || EEXIST == errno) ? 0 : -1;
fail:
        free(pathname);
        free(parent);
        return -1;
#endif
    }

    static void CopyFile(const string &src_file, const string &dst_file)
    {
        // cout << "copy from: " << src_file << " to: " << dst_file << endl;
        ifstream f1(src_file.c_str(), fstream::binary);
        ofstream f2(dst_file.c_str(), fstream::in | fstream::out | fstream::trunc | fstream::binary);
        f2 << f1.rdbuf();
    }

    static int IsFileExist(const char *file)
    {
        struct stat buf;
        return !stat(file, &buf);
    }

    static int EqualFiles(const char *file1, const char *file2)
    {
        FILE *f1, *f2;
        f1 = fopen(file1, "rb");
        f2 = fopen(file2, "rb");

        if (f1 == NULL || f2 == NULL)
            return 0;

        int N = 4096;
        char buf1[N];
        char buf2[N];

        do
        {
            size_t r1 = fread(buf1, 1, N, f1);
            size_t r2 = fread(buf2, 1, N, f2);
            if (r1 != r2 || memcmp(buf1, buf2, r1))
            {
                return 0;
            }
        }
        while (!feof(f1) || !feof(f2));
        return 1;
    }

    /* 0: simple add/replace; 1: bsdiff; 2: courgette */
    static int ProcessFile(const string &old_file, const string &new_file, const string &patch_file)
    {
        if (IsFileExist(old_file.c_str()))
        {
            if (!IsFileExist(new_file.c_str()))
            {
                return 2; // Delete
            }
            if (!EqualFiles(old_file.c_str(), new_file.c_str()))
            {
                GenPatch<DiffAlgorithmBSDiff> GenPatch;
                GenPatch.Do(old_file, new_file, patch_file);
                return 1; // Update, here BSDiff
            }
        }
        CopyFile(new_file, patch_file);  // copy if necessary (to make a complete version directory)
        return 0; // Replace
    }

    void OnDir(const string &flag, const string &path)
    {
        if (flag == "New")
        {

        }
        else
        {

        }

    }

    void OnFile(const string &flag, const string &path)
    {
        if (flag == "New")
        {

        }
        else
        {

        }

    }

    int Traverse(const string &flag)
    {
        DIR*	handle;
        stack<string> FolderSet;
        string CurrentFile, CurrentFolder;
        //string CurrentFileSrc, CurrentFolderSrc;
        struct stat buf;
        struct dirent* direntp;

        string this_dir;
        if (flag == "New")
            this_dir = _new_dir;
        else
            this_dir = _old_dir;
        FolderSet.push(this_dir);

        while (!FolderSet.empty())
        {
            CurrentFolder = FolderSet.top();
            FolderSet.pop(); // the last directory

            if ((handle = opendir(CurrentFolder.c_str())) == NULL)
                continue;
            else
            {
                while ((direntp = readdir(handle)) != NULL)
                {
                    if ((direntp->d_name[0] == '.' && (direntp->d_name[1] == '\0' || (direntp->d_name[1] == '.' && direntp->d_name[2] == '\0'))))
                        continue;
                    CurrentFile = CurrentFolder + "/" + direntp->d_name;
                    if (stat(CurrentFile.c_str(), &buf) != 0)
                        continue;
                    /* create patch folders if do not exist */
                    string old_file, new_file, patch_file, relative_path;
                    relative_path = CurrentFile.substr(this_dir.length() + 1);
                    if (flag == "New")
                    {
                        new_file = CurrentFile;
                        old_file = _old_dir + '/' + relative_path;
                    }
                    else
                    {
                        new_file = _new_dir + '/' + relative_path;
                        old_file = CurrentFile;
                    }
                    FileItem fi;
                    fi.Path = relative_path;

                    if (S_ISDIR(buf.st_mode))
                    {
                        if (flag == "New")
                        {
                            FolderSet.push(CurrentFile);
                            string patch_folder;
                            patch_folder = _patch_dir + '/' + relative_path;
                            pear_mkdirp(patch_folder.c_str(), 0777);
                        }
                        else
                        {
                            if (!IsFileExist(new_file.c_str()))
                            {

                                fi.Operation = "DeleteDir";
                                // just do not push
                                _UpdateDesc.AddFileItem(fi);
                            }
                        }
                    }
                    else
                    {
                        if (flag == "New")
                        {
                            patch_file = _patch_dir + '/' + relative_path + ".patch";
                            _new_files.push_back(CurrentFile);
                            int ret = ProcessFile(old_file, new_file, patch_file);
                            char mode[8];
                            snprintf(mode, 8, "0%o", buf.st_mode);
                            fi.Permissions = mode;
                            unsigned long m;
                            m = strtoul(mode, NULL, 0);
                            cout << buf.st_mode << " " << mode << " " << m << endl;
                            fi.Size = to_string(buf.st_size);  // this is C++ 11
                            if (0 == ret)
                            {
                                fi.Operation = "Replace";
                            }
                            else if (1 == ret)
                            {
                                fi.Operation = "BSDiff";
                            }
                            else if (2 == ret)
                            {
                                fi.Operation = "DeleteFile";
                            }
                            _UpdateDesc.AddFileItem(fi);
                        }
                        else
                        {
                            if (!IsFileExist(new_file.c_str()))
                            {
                                fi.Operation = "DeleteFile";
                                _UpdateDesc.AddFileItem(fi);
                            }
                        }
                    }
                }
            }
            closedir(handle);
        }
        WriteUpdateDesc();
        return 0;
    }

    void Do()
    {
        Traverse("New");
        Traverse("Old");
    }
};

#endif // GenPatches_HPP
